#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "Isrdpc.h"
#include "Cpci7841.h"
#include <drv/pci/pciConfigLib.h>

#define	CPCI7841_VENDOR_ID		(0x144A)
#define	CPCI7841_DEVICE_ID		(0x7841)

static int g_DeviceNumber = 0;

//instance for every device_open
PCI_Info *g_PciInfo[MAX_PCI_CARDS];

void dumpBaseAddresses(int busNo, int deviceNo, int funcNo)
{
	unsigned int base_address_0,base_address_1,base_address_2,base_address_3,base_address_4,base_address_5;
	pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_0, &base_address_0);
	pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_1, &base_address_1);
	pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_2, &base_address_2);
	pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_3, &base_address_3);
	pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_4, &base_address_4);
	pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_5, &base_address_5);
	
	printf("begin base addresses dumping.\n");
	printf("\tPCI_CFG_BASE_ADDRESS_0:%08X\n",base_address_0);
	printf("\tPCI_CFG_BASE_ADDRESS_1:%08X\n",base_address_1);
	printf("\tPCI_CFG_BASE_ADDRESS_2:%08X\n",base_address_2);
	printf("\tPCI_CFG_BASE_ADDRESS_3:%08X\n",base_address_3);
	printf("\tPCI_CFG_BASE_ADDRESS_4:%08X\n",base_address_4);
	printf("\tPCI_CFG_BASE_ADDRESS_5:%08X\n",base_address_5);
}

//Initialize the module - Register the character device 
static int AdlFindDevice(U16 vendorId, U16 deviceId)
{
	U8 irqNo = 0;
	int busNo = 0;
	int funcNo = 0;
	int deviceNo = 0;
	int pciIndex = 0;
	PCI_Info* tempPciInfo;
    unsigned int  baseAddr, lcrBase;    

	for(pciIndex = 0; pciIndex < 32; )
	{
		if (pciFindDevice(vendorId, deviceId, pciIndex, &busNo, &deviceNo, &funcNo) != OK)
		{
		    break;
		}

		pciConfigInByte(busNo, deviceNo, funcNo, PCI_CFG_DEV_INT_LINE, &irqNo) ;
		pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_2, &baseAddr); //notice
		pciConfigInLong(busNo, deviceNo, funcNo, PCI_CFG_BASE_ADDRESS_1, &lcrBase);  //notice
		//dumpBaseAddresses(busNo, deviceNo, funcNo);
		tempPciInfo = (PCI_Info*)kmalloc(sizeof(PCI_Info), GFP_KERNEL);
				
		//allocate failed! just return the current index
		if(!tempPciInfo)
		{
			return pciIndex;
		}
				
		//Clear structure        
		memset(tempPciInfo, '\0', sizeof(PCI_Info));
		
		tempPciInfo->address0 = lcrBase & (~0x03);
		tempPciInfo->address1 = baseAddr & (~0x03);
		
		tempPciInfo->irqNo = irqNo;
		tempPciInfo->initFlag = deviceId;
		tempPciInfo->wCard = pciIndex;
			
		printf("7841-%d device found with Addr0:[%lx] addr1:[%lx] IRQ:[%d]\n",pciIndex, tempPciInfo->address0, tempPciInfo->address1, tempPciInfo->irqNo);
			
		// asigning the address into pointer array
		g_PciInfo[pciIndex] = tempPciInfo;
		
		pciIndex ++;
	} 
	
    return pciIndex; 
}

int OpenCpci7841(int boardNo)
{
	int i;
	UCHAR reg;
	PCI_Info * ppci_info;
	printf("g_DeviceNumber:%d\n",g_DeviceNumber);
	
	
	if (g_DeviceNumber == 0)
	{
		for(i = 0; i < MAX_PCI_CARDS; i++)
		{
			g_PciInfo[i] = NULL;
		}
		
		g_DeviceNumber = AdlFindDevice(CPCI7841_VENDOR_ID, CPCI7841_DEVICE_ID);
		if(g_DeviceNumber <= 0)
		{
			printf("Error in AdlFindDevice : no card\n");
			return (-ENODEV); 
		}
	}
	ppci_info = g_PciInfo[boardNo];

	if(boardNo >= g_DeviceNumber)
	{
		return -1;
	}

	if(ppci_info->reference > 0)
	{
		// multi-open supported,initialize once
		ppci_info->reference++;
		return 0;
	}

	ppci_info->initFlag = 0x7841;
	ppci_info->reference++;
	

	// Spin lock for receiving buffers
	SpinLockInit(&ppci_info->rlock[0]);
	SpinLockInit(&ppci_info->rlock[1]);
	
	// Disable interrupt first
	reg = Inbyte(ppci_info->address0 + ADL_OP_LINTCSR);
	reg &= ~(1<<6);
	Outbyte(ppci_info->address0 + ADL_OP_LINTCSR, reg);
	
	// request interrupts,and then enable interrupt	
	if(ppci_info->irqNo != 0)
	{
		if(OK != RequestIrq(ppci_info->irqNo,Cpci7841InterruptHandler,boardNo))
		{
			//unsuccess irq request
			ppci_info->isrEnable = FALSE;
		}
		else
		{
			reg = Inbyte( ppci_info->address0 + ADL_OP_LINTCSR );
			ppci_info->isrEnable = TRUE;
			
			// Enable interrupts
			reg |= (1<<6);
			Outbyte( ppci_info->address0 + ADL_OP_LINTCSR, reg);
			printf("Interrupt register success!\n");
		}
	}
	
	return boardNo;
}

int CloseCpci7841(int boardNo)
{
	UCHAR reg;
	PCI_Info* ppci_info = g_PciInfo[boardNo];
	
	ppci_info->reference--;
	
	if(ppci_info->reference > 0)
	{
		return 0;
	}
	
	// Disable interrupt when no more application open this driver
	ppci_info->initFlag = FALSE;
	
	// Disable interrupts
	reg = Inbyte(ppci_info->address0 + ADL_OP_LINTCSR);
	reg &= ~(1<<6);
	Outbyte(ppci_info->address0 + ADL_OP_LINTCSR, reg);
	
	if((ppci_info->irqNo != 0) && (ppci_info->isrEnable == TRUE)) 
	{
		// Free irq
		FreeIrq(ppci_info->irqNo , ppci_info);
	}
	
	return 0;
}

void SndMsg(PCI_Info* pDecExt, int port, CAN_MSG* temp_param)
{
	UCHAR v;
	UCHAR count;
	UCHAR dataStart;
	
	// Transmit buffer in VXD was full
	if((pDecExt->sPtrL[port] == pDecExt->sPtrU[port]) && (pDecExt->sCnt[port] == FIFOSIZE))
	{
		temp_param->retVal = (LONG) -1;
		return;
	}
	else
	{
		memcpy(&(pDecExt->sBuf[port][pDecExt->sPtrU[port]]),&(temp_param->msg),sizeof(CAN_PACKET));  
		pDecExt->sPtrU[port]++;
		pDecExt->sCnt[port]++; 
		if(pDecExt->sPtrU[port] == FIFOSIZE)
		{
			pDecExt->sPtrU[port]=0;
		}
		
		// Write to the Chip
		v = Inbyte(pDecExt->address1+(128 * port)+2);
		
		// Transmit buffer is busy
		if (!(v & 0x04))
		{
			// 2007/03/22 bug found
			// Roll back buffer. If not,
			// device will send previous message
			// after sending message failed.
			if(pDecExt->sPtrU[port] == 0)
			{
				pDecExt->sPtrU[port] = FIFOSIZE;	
			}
			else
			{
				pDecExt->sPtrU[port]--;
			}
			
			pDecExt->sCnt[port]--;
			temp_param->retVal = -2;
			
			return;
		}
		
		// Standards frame
		if (pDecExt->portMode[port] == 0)
		{
			// eff 19-bit ID
			dataStart = 19;
			
			if (pDecExt->sBuf[port][pDecExt->sPtrL[port]].rtr == 0)
			{
				Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].len & 0x0F),(pDecExt->address1+(128 * port)+16));
			}
			else
			{
				Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].len | 0x40),(pDecExt->address1+(128 * port)+16));
			}
			
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].canId << 5),(pDecExt->address1+(128 * port)+18));
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].canId >>3),(pDecExt->address1+(128 * port)+17));
		}
		else
		{
			// eff 29-bit ID
			dataStart = 21;
			
			if (pDecExt->sBuf[port][pDecExt->sPtrL[port]].rtr == 0)
			{
				Outbyte((UCHAR)((pDecExt->sBuf[port][pDecExt->sPtrL[port]].len & 0x0F) | 0x80), (pDecExt->address1+(128 * port)+16));
			}
			else
			{
				Outbyte((UCHAR)((pDecExt->sBuf[port][pDecExt->sPtrL[port]].len & 0x0F) | 0x40 | 0x80),(pDecExt->address1+(128 * port)+16));
			}
			
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].canId >> 21),(pDecExt->address1+(128 * port)+17));
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].canId >> 13),(pDecExt->address1+(128 * port)+18));
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].canId >> 5),(pDecExt->address1+(128 * port)+19));
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].canId << 3),(pDecExt->address1+(128 * port)+20));
		}
		
		for(count=0;count < pDecExt->sBuf[port][pDecExt->sPtrL[port]].len; count++)
		{
			Outbyte((UCHAR)(pDecExt->sBuf[port][pDecExt->sPtrL[port]].data[count]),(pDecExt->address1+(128 * port)+dataStart+count));		
		}
		
		// Request transmission
		Outbyte(0x01,(pDecExt->address1+(128 * port)+1));
		
		pDecExt->sPtrL[port]++;
		pDecExt->sCnt[port]--; 
		
		if(pDecExt->sPtrL[port] == FIFOSIZE)
		{
			pDecExt->sPtrL[port] = 0;
		}
		
		temp_param->retVal = 0;
	}
}

void RevMsg(PCI_Info* pDecExt, int port, CAN_MSG* tempParam)
{
	int rCnt;
	unsigned long flags;

	SpinLockIrqsave(&pDecExt->rlock[port], flags);
		
	if(pDecExt->rPtrL[port] != pDecExt->rPtrU[port])
	{
		memcpy(&(tempParam->msg),&(pDecExt->rBuf[port][pDecExt->rPtrL[port]]),sizeof(CAN_PACKET));
		pDecExt->rPtrL[port]++;
		
		if(pDecExt->rPtrL[port] == FIFOSIZE)
		{
			pDecExt->rPtrL[port] = 0;
		}
		
		rCnt = (int)pDecExt->rPtrU[port] - pDecExt->rPtrL[port];
		if(rCnt < 0)
		{
			rCnt += FIFOSIZE;
		}
		
		tempParam->retVal = rCnt;
	}
	else
	{
		tempParam->retVal = (LONG) -1;
	}
	
	SpinUnlockIrqrestore(&pDecExt->rlock[port], flags);
}

int Can7841Ioctl(int boardNo, unsigned int ioctlNum, unsigned long ioctlParam)
{
	int rCnt;
	unsigned long flags;
	unsigned long port;
	PCI_Info* ppci_info = g_PciInfo[boardNo];
	
	switch (ioctlNum)
	{
	
	case IOCTL_7841_REG_READ:
		((REG_RW*)ioctlParam)->data = Inbyte(ppci_info->address1 + ((REG_RW*)ioctlParam)->offset);
		break;
		
	case IOCTL_7841_REG_WRITE:
		Outbyte(((REG_RW*)ioctlParam)->data,ppci_info->address1 + ((REG_RW*)ioctlParam)->offset);
		break;
		
	case IOCTL_7841_CLOSE_PORT:
		Outbyte(((REG_RW*)ioctlParam)->data,ppci_info->address1 + ((REG_RW*)ioctlParam)->offset);
		break;
		
	case IOCTL_7841_CLOSE_CARD:
		Outbyte(((REG_RW*)ioctlParam)->data,ppci_info->address1 + ((REG_RW*)ioctlParam)->offset);
		break;
		
	case IOCTL_7841_SET_BUFFER_DATA:
		SndMsg(ppci_info, ((((CAN_MSG*)ioctlParam)->handle)%2), (CAN_MSG*)ioctlParam);	//notice
		break;
		
	case IOCTL_7841_GET_BUFFER_DATA:
		RevMsg(ppci_info,(((CAN_MSG*)ioctlParam)->handle%2), (CAN_MSG*)ioctlParam);  //notice
		break;
		
	case IOCTL_7841_SET_PORT_MODE:
		ppci_info->portMode[((RETINFO*)ioctlParam)->handle % 2] = ((RETINFO*)ioctlParam)->mode;
		break;
		
	case IOCTL_7841_GET_UNRD_COUNT:
		port = ((REC_CN*)ioctlParam)->handle % 2;
		SpinLockIrqsave(&ppci_info->rlock[port], flags);
		rCnt = (int)ppci_info->rPtrU[port] - ppci_info->rPtrL[port];
		SpinUnlockIrqrestore(&ppci_info->rlock[port], flags);
		
		if(rCnt < 0)
		{
			rCnt += FIFOSIZE;	
		}
		
		((REC_CN*)ioctlParam)->retVal = rCnt;
		break;
			
	case IOCTL_7841_CLEAR_RX_BUFFER:
		port = *((unsigned long*)ioctlParam) % 2;
		SpinLockIrqsave(&ppci_info->rlock[port], flags);
		ppci_info->rPtrU[port] = 0;
		ppci_info->rPtrL[port] = 0;
		SpinUnlockIrqrestore(&ppci_info->rlock[port], flags);
		break;
		
	case IOCTL_7841_CLEAR_TX_BUFFER:
		ppci_info->sPtrU[*((int*)ioctlParam) % 2] = 0;
		ppci_info->sPtrL[*((int*)ioctlParam) % 2] = 0;
		ppci_info->sCnt[*((int*)ioctlParam) % 2] = 0;
		break;

	default :
		return -1;
	}
	
	return 0;
}
